package com.gaobug.base.utils.redis;

import com.gaobug.base.cache.constant.CacheNames;
import com.gaobug.base.exception.BusinessException;
import com.gaobug.base.utils.spring.SpringContextUtils;
import com.gaobug.base.constant.CharSequence;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author gaobug
 */
@Slf4j
public class RedisUtil {

    @SuppressWarnings("unchecked")
    public static final RedisTemplate<String, Object> REDIS_TEMPLATE = SpringContextUtils.getBean("redisTemplate",
            RedisTemplate.class);

    public static final StringRedisTemplate STRING_REDIS_TEMPLATE = SpringContextUtils.getBean("stringRedisTemplate",
            StringRedisTemplate.class);

    // =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return 是否成功
     */
    public static Boolean expire(String key, long time) {
        if (key.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }
        try {
            if (time > 0) {
                REDIS_TEMPLATE.expire(key, time, TimeUnit.SECONDS);
            }
            return Boolean.TRUE;
        } catch (Exception e) {
            log.error("Set expire error: {}", e.getMessage());
            return Boolean.FALSE;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回-1代表为永久有效 失效时间为0，说明该主键未设置失效时间（失效时间默认为-1）
     */
    public static Long getExpire(String key) {
        if (key.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }
        return REDIS_TEMPLATE.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false 不存在
     */
    public static Boolean hasKey(String key) {
        if (key.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }
        try {
            return REDIS_TEMPLATE.hasKey(key);
        } catch (Exception e) {
            log.error("Error getting hasKey: {}", e.getMessage());
            return Boolean.FALSE;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public static void del(String... key) {
        if (key != null && key.length > 0) {
            for (String s : key) {
                if (s.contains(CharSequence.SPACE)) {
                    throw new BusinessException("非法key值");
                }
            }

            if (key.length == 1) {
                REDIS_TEMPLATE.delete(key[0]);
            } else {
                REDIS_TEMPLATE.delete(Arrays.asList(key));
            }
        }
    }

    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    @SuppressWarnings("unchecked")
    public static <T> T get(String key) {
        if (key.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }
        return (T) REDIS_TEMPLATE.opsForValue().get(key);
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public static boolean set(String key, Object value, long time) {
        if (key.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }
        try {
            if (time > 0) {
                REDIS_TEMPLATE.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                REDIS_TEMPLATE.opsForValue().set(key, value);
            }
            return true;
        } catch (Exception e) {
            log.error("Redis opsForValue error: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 递增 此时value值必须为int类型 否则报错
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return 自增后的值
     */
    public static Long incr(String key, long delta) {
        if (key.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return STRING_REDIS_TEMPLATE.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return 自减后的值
     */
    public static Long decr(String key, long delta) {
        if (key.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }
        if (delta < 0) {
            throw new RuntimeException("递减因子必须小于0");
        }
        return STRING_REDIS_TEMPLATE.opsForValue().increment(key, -delta);
    }

    public static boolean setLongValue(String key, Long value, long time) {
        if (key.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }
        try {
            if (time > 0) {
                STRING_REDIS_TEMPLATE.opsForValue().set(key, String.valueOf(value), time, TimeUnit.SECONDS);
            } else {
                STRING_REDIS_TEMPLATE.opsForValue().set(key, String.valueOf(value));
            }
            return true;
        } catch (Exception e) {
            log.error("setLongValue() error: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public static Long getLongValue(String key) {
        if (key == null) {
            return null;
        }
        if (key.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }
        String result = STRING_REDIS_TEMPLATE.opsForValue().get(key);
        if (result == null) {
            return null;
        }
        return Long.valueOf(result);
    }

    /**
     * 批量删除缓存
     *
     * @param keys
     */
    public static void deleteBatch(List<String> keys) {
        if (ObjectUtils.isEmpty(keys)) {
            return;
        }
        for (String key : keys) {
            if (key.contains(CharSequence.SPACE)) {
                throw new BusinessException("非法key值");
            }
        }
        REDIS_TEMPLATE.delete(keys);
    }

    /**
     * 批量删除缓存
     *
     * @param cacheName 缓存名
     * @param cacheKeys 缓存key
     */
    public static void deleteBatch(String cacheName, List<?> cacheKeys) {
        if (!StringUtils.hasText(cacheName) || ObjectUtils.isEmpty(cacheKeys)) {
            return;
        }
        List<String> strCacheKeys = cacheKeys.stream().map(String::valueOf).collect(Collectors.toList());
        List<String> keys = new ArrayList<>();
        for (String cacheKey : strCacheKeys) {
            String key = cacheName + CacheNames.UNION + cacheKey;
            keys.add(key);
            if (key.contains(CharSequence.SPACE)) {
                throw new BusinessException("非法key值");
            }
        }
        REDIS_TEMPLATE.delete(keys);
    }

    /**
     * 比较和删除标记，原子性
     *
     * @return 是否成功
     */
    public static boolean cad(String key, String value) {

        if (key.contains(CharSequence.SPACE) || value.contains(CharSequence.SPACE)) {
            throw new BusinessException("非法key值");
        }

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        //通过lure脚本原子验证令牌和删除令牌
        Long result = STRING_REDIS_TEMPLATE.execute(new DefaultRedisScript<Long>(script, Long.class),
                Collections.singletonList(key),
                value);

        return !Objects.equals(result, 0L);
    }
    /* -------------------hash相关操作------------------------- */

    /**
     * 获取存储在哈希表中指定字段的值
     *
     * @param key
     * @param field
     * @return
     */
    public static Object hGet(String key, String field) {
        return REDIS_TEMPLATE.opsForHash().get(key, field);
    }

    /**
     * 获取所有给定字段的值
     *
     * @param key
     * @return
     */
    public static Map<Object, Object> hGetAll(String key) {
        return REDIS_TEMPLATE.opsForHash().entries(key);
    }
    /**
     * 获取所有给定字段的值
     *
     * @param key
     * @return
     */
    public static Cursor<Map.Entry<Object, Object>> hGetCursor(String key) {
        return REDIS_TEMPLATE.opsForHash().scan(key,ScanOptions.NONE);
    }
    /**
     * 获取所有给定字段的值
     *
     * @param key
     * @param fields
     * @return
     */
    public static List<Object> hMultiGet(String key, Collection<Object> fields) {
        return REDIS_TEMPLATE.opsForHash().multiGet(key, fields);
    }

    /**
     * 设置值
     *
     * @param key
     * @param hashKey
     * @param value
     */
    public static void hPut(String key, String hashKey, Object value) {
        REDIS_TEMPLATE.opsForHash().put(key, hashKey, value);
    }

    public static void hPutAll(String key, Map<String, String> maps) {
        REDIS_TEMPLATE.opsForHash().putAll(key, maps);
    }

    /**
     * 仅当hashKey不存在时才设置
     *
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public static Boolean hPutIfAbsent(String key, String hashKey, String value) {
        return REDIS_TEMPLATE.opsForHash().putIfAbsent(key, hashKey, value);
    }

    /**
     * 删除一个或多个哈希表字段
     *
     * @param key
     * @param fields
     * @return
     */
    public static Long hDelete(String key, Object... fields) {
        return REDIS_TEMPLATE.opsForHash().delete(key, fields);
    }

    /**
     * 查看哈希表 key 中，指定的字段是否存在
     *
     * @param key
     * @param field
     * @return
     */
    public static boolean hExists(String key, String field) {
        return REDIS_TEMPLATE.opsForHash().hasKey(key, field);
    }

    /**
     * 为哈希表 key 中的指定字段的整数值加上增量 increment
     *
     * @param key
     * @param field
     * @param increment
     * @return
     */
    public static Long hIncrBy(String key, Object field, long increment) {
        return REDIS_TEMPLATE.opsForHash().increment(key, field, increment);
    }

    /**
     * 为哈希表 key 中的指定字段的整数值加上增量 increment
     *
     * @param key
     * @param field
     * @param delta
     * @return
     */
    public static Double hIncrByFloat(String key, Object field, double delta) {
        return REDIS_TEMPLATE.opsForHash().increment(key, field, delta);
    }

    /**
     * 获取所有哈希表中的字段
     *
     * @param key
     * @return
     */
    public static Set<Object> hKeys(String key) {
        return REDIS_TEMPLATE.opsForHash().keys(key);
    }

    /**
     * 获取哈希表中字段的数量
     *
     * @param key
     * @return
     */
    public static Long hSize(String key) {
        return REDIS_TEMPLATE.opsForHash().size(key);
    }

    /**
     * 获取哈希表中所有值
     *
     * @param key
     * @return
     */
    public static List<Object> hValues(String key) {
        return REDIS_TEMPLATE.opsForHash().values(key);
    }

    /**
     * 迭代哈希表中的键值对
     *
     * @param key
     * @param options
     * @return
     */
    public static Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
        return REDIS_TEMPLATE.opsForHash().scan(key, options);
    }

}
